#!/usr/bin/python
# -*- coding: utf-8 -*-
# @name             : 页面基本操作
# @author           : OneYoung
# @create           : 2024/02/04
# @version          : v1.0
# @desc             : 页面基本操作封装类

import os
from application.settings import BASE_URL, TIMEOUT
from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.select import Select
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as exp_cond
from utils.log_record import logger, log_path
from utils.get_time import digital_datetime
from selenium.common import TimeoutException, NoSuchElementException


# 基础页面类
class BasePage:

    def __init__(self, driver):
        """每次启动都先初始化界面"""
        self.driver = driver
        self.action = ActionChains(driver)

    def save_screenshot(self, file_name):
        """
        保存截图到日志文件目录

        :param file_name: 截图文件名称
        :return:
        """
        # 截图保存路径，存放到日志目录中
        screenshot_dir = os.path.join(log_path, 'screenshot')
        if not os.path.exists(screenshot_dir):
            os.makedirs(screenshot_dir)

        # 保存截图
        file_path = os.path.join(screenshot_dir, file_name)
        try:
            self.driver.save_screenshot(file_path)
        except Exception as ec:
            logger.error(f'截图[{file_name}]保存失败.')
            raise ec

    def open_page(self, router: str):
        """打开页面
        :param router: 页面路由
        :return:
        """
        url = BASE_URL + router
        try:
            self.driver.get(url)
        except Exception as ec:
            logger.error(f'打开页面[{url}]失败.{ec}')
            raise ec

    def find_element(self, loc):
        """
        定位页面元素

        :param loc: 元素的定位器
        :return:
        """
        try:
            return self.driver.find_element(*loc)
        except Exception as ec:
            err_desc = f'{loc}元素定位失败.{ec}'
            logger.error(err_desc)
            raise ValueError(err_desc)

    def find_wait_element(self, loc, timeout=TIMEOUT):
        """
        定位页面元素，未获取到时切换显示等待定位

        :param loc: 元素的定位器
        :param timeout: 超时时间
        :return:
        """
        try:
            return self.driver.find_element(*loc)
        except NoSuchElementException:
            try:
                return self.wait_element_visible(loc, timeout)
            except TimeoutException:
                raise ValueError(f'查找并等待定位元素失败：{loc}')

    def find_elements(self, loc):
        """
        定位一组页面元素

        :param loc: 元素的定位器
        :return:
        """
        try:
            return self.driver.find_elements(*loc)
        except Exception as ec:
            err_desc = f'{loc}元素定位失败.{ec}'
            logger.error(err_desc)
            raise ValueError(err_desc)

    def click_element(self, loc):
        """
        点击元素

        :param loc: 元素的定位器
        :return:
        """
        self.find_wait_element(loc).click()

    def double_click_element(self, loc):
        """
        双击元素

        :param loc: 元素的定位器
        :return:
        """
        el = self.find_wait_element(loc)
        self.action.double_click(el).perform()

    def right_click_element(self, loc):
        """
        点击鼠标右键

        :param loc: 元素的定位器
        :return:
        """
        el = self.find_wait_element(loc)
        self.action.context_click(el).perform()

    def input_value(self, loc, *args):
        """
        输入框输入内容

        :param loc: 元素的定位器
        :param args: 输入的内容
        :return:
        """
        self.find_wait_element(loc).send_keys(*args)

    def clear_value(self, loc):
        """
        清空输入框内容

        :param loc: 元素的定位器
        :return:
        """
        self.find_wait_element(loc).clear()

    def backspace_clear(self, loc):
        """
        通过backspace键盘清空输入内容

        :param loc: 元素的定位器
        :return:
        """
        # ctrl+a全选后再按backspace删除
        self.input_value(loc, Keys.CONTROL, 'a')
        self.input_value(loc, Keys.BACKSPACE)

    def get_element_text(self, loc):
        """
        获取元素文本内容

        :param loc: 元素的定位器
        :return:
        """
        return self.find_wait_element(loc).text

    def get_element_attribute(self, loc, attr):
        """
        获取元素属性

        :param loc: 元素的定位器
        :param attr: 选择要获取的属性值名称
        :return:
        """
        self.find_wait_element(loc).get_attribute(attr)

    def wait_element_visible(self, loc, timeout=TIMEOUT):
        """
        显示等待：等待元素可见

        :param loc: 元素的定位器
        :param timeout: 等待超时时间(s)
        :return:
        """
        try:
            wait = WebDriverWait(self.driver, timeout)
            el = wait.until(exp_cond.visibility_of_element_located(loc))
            return el
        except Exception as ec:
            logger.error(f'等待{loc}元素可见失败.{ec}')
            raise ec

    def wait_element_clickable(self, loc, timeout=TIMEOUT):
        """
        显示等待：等待元素可点击

        :param loc: 元素的定位器
        :param timeout: 等待超时时间(s)
        :return:
        """
        try:
            wait = WebDriverWait(self.driver, timeout)
            el = wait.until(exp_cond.element_to_be_clickable(loc))
            return el
        except Exception as ec:
            logger.error(f'等待{loc}元素可点击失败.{ec}')
            raise ec

    def wait_element_exist(self, loc, name, timeout=TIMEOUT):
        """
        显示等待：等待元素存在

        :param loc: 元素的定位器
        :param name: 定位元素名称
        :param timeout: 等待超时时间(s)
        :return:
        """
        try:
            wait = WebDriverWait(self.driver, timeout)
            el = wait.until(exp_cond.presence_of_element_located(loc))
            return el
        except Exception as ec:
            logger.error(f'等待{loc}元素存在失败.{ec}')
            # 失败截图
            file_name = f"{digital_datetime()}-[{name}]等待元素存在失败.png"
            self.save_screenshot(file_name)
            raise ec

    def wait_element_iframe_switch_loc(self, loc, timeout=TIMEOUT):
        """
        显示等待：加载并切换到iframe中

        :param loc: 元素的定位器
        :param timeout: 等待超时时间(s)
        :return:
        """
        try:
            wait = WebDriverWait(self.driver, timeout)
            el = wait.until(exp_cond.frame_to_be_available_and_switch_to_it(loc))
            return el
        except Exception as ec:
            logger.error(f'等待iframe可见并切换{loc}失败.{ec}')
            raise ec

    def switch_iframe(self, name):
        """
        iframe切换

        :param name: iframe标签的name属性
        :return:
        """
        try:
            self.driver.switch_to.frame(name)
        except Exception as ec:
            logger.error(f'切换iframe标签{name}失败.{ec}')
            raise ec

    def execute_script(self, script, *args):
        """
        运行脚本代码

        :param script: 脚本代码
        :param args: 脚本参数
        :return:
        """
        return self.driver.execute_script(script, *args)

    def get_current_window_handle(self):
        """获取当前窗口句柄"""
        return self.driver.current_window_handle

    def get_window_handles(self):
        """获取所有窗口句柄"""
        return self.driver.window_handles

    def switch_to_window(self, serial=0):
        """
        切换窗口

        :param serial: 窗口序号，默认切换到第一个窗口
        """
        all_windows = self.driver.window_handle
        self.driver.switch_to.window(all_windows[serial])

    def move_to_element(self, loc):
        """
        将鼠标移动到元素上

        :param loc: 定位元素
        """
        el = self.find_wait_element(loc)
        self.action.move_to_element(el).perform()

    def move_and_click_element(self, loc):
        """
        移动到元素上方并点击

        :param loc: 定位元素
        """
        self.move_to_element(loc)
        self.click_element(loc)

    def drag_and_drop(self, start_loc, end_loc):
        """
        拖动元素

        :param start_loc: 拖动起始位置
        :param end_loc: 拖动结束位置
        """
        start = self.find_wait_element(start_loc)
        end = self.find_wait_element(end_loc)
        self.action.drag_and_drop(start, end).perform()

    def click_by_offset(self, loc, x, y):
        """
        通过坐标位置点击

        :param loc: 定位元素
        :param x: 目标位置的X偏移量
        :param y: 目标位置的Y偏移量
        """
        el = self.wait_element_visible(loc, TIMEOUT)
        self.action.move_to_element_with_offset(el, x, y).click().perform()

    def drag_and_drop_by_offset(self, loc, x, y):
        """
        拖动元素

        :param loc: 拖动元素
        :param x: 目标位置的X偏移量
        :param y: 目标位置的Y偏移量
        """
        el = self.find_wait_element(loc)
        self.action.click_and_hold(el)  # 鼠标按下并保持
        self.action.drag_and_drop_by_offset(el, x, y).perform()

    def select_by_index(self, loc, index):
        """
        根据元素下标选择

        :param loc: 定位元素
        :param index: 下标序号
        """
        el = self.find_wait_element(loc)
        Select(el).select_by_index(index)

    def select_by_value(self, loc, value):
        """
        根据元素value值选择

        :param loc: 定位元素
        :param value: 元素值
        """
        el = self.find_wait_element(loc)
        Select(el).select_by_value(value)

    def select_by_visible_text(self, loc, visible_text):
        """
        根据元素可见值选择

        :param loc: 定位元素
        :param visible_text: 可见元素值
        """
        el = self.find_wait_element(loc)
        Select(el).select_by_visible_text(visible_text)

    def page_scroll(self, bottom=False, distance=(0, 1000)):
        """
        滚动页面

        :param bottom: 是否直接滚动到当前页面的最底部，默认False
        :param distance: 滚动距离(x, y)，x左右滚动，y上下滚动，默认向下滚动1000像素
        """
        if bottom:
            self.driver.execute_script("window.scrollTo(document.body.scrollHeight)")
        else:
            self.driver.execute_script(f"window.scrollTo({distance[0]}, {distance[1]})")

    def popup_window_operate(self, action=True, send=None, text=False):
        """
        弹窗操作

        :param action: 弹窗信息是否点击确定，False点击取消，默认True
        :param send: 弹窗文本框输入信息
        :param text: 是否获取弹窗中的文本信息
        """
        if send:
            self.driver.switch_to.alert.send_keys(send)
        if text:
            return self.driver.switch_to.alert.text
        if action:
            self.driver.switch_to.alert.accept()  # 点击确定
        else:
            self.driver.switch_to.alert.dismiss()  # 点击取消
        return None

    def get_title(self):
        """获取当前页面标题"""
        return self.driver.title

    def get_current_url(self):
        """获取当前页面地址"""
        return self.driver.current_url

    def get_page_source(self):
        """获取页面源码"""
        return self.driver.page_source

    def get_cookies(self):
        """获取所有cookie"""
        return self.driver.get_cookies()

    def add_cookies(self, cookie: dict):
        """
        添加cookie

        :param cookie: cookie信息，键值对，如｛"name": "cookie_name", "value": "cookie_value"｝
        """
        self.driver.add_cookie(cookie)
        self.driver.refresh()  # 刷新页面以应用Cookie（如果需要）

    def back(self):
        """页面后退"""
        self.driver.back()

    def forward(self):
        """页面前进"""
        self.driver.forward()

    def refresh(self):
        """页面刷新"""
        self.driver.refresh()

    def close(self):
        """页面关闭"""
        self.driver.close()
